/*
 *  第四节  魔方宇宙
 */


import React,{useRef,useEffect,useCallback} from 'react'
import * as THREE from 'three'
import { BufferGeometry, Mesh } from 'three'
import './index.scss'

export default function Page() {

    const Body = useRef()
    // 当 React 附加了 ref 属性之后，ref.current 将直接指向 <button> DOM 元素实例。
    // 创建场景
    const Scene = useRef(new THREE.Scene()).current
    // 创建透视相机
    const Camera = useRef(new THREE.PerspectiveCamera()).current
    // 创建渲染器
    const Render = useRef(new THREE.WebGLRenderer(
        {antialias:true}
    )).current
    //创建物体组
    const Meshs = useRef([]).current
    // 创建灯光组
    const Lights = useRef([]).current
    // 我们的id等于这个循环函数返回的一个帧循环函数对应的ID
    const id = useRef(null)
    //
    const isDown = useRef(false)
    //
    const pi = useRef(30)
    //
    const R = useRef(90)
    // 地板
    const Floor = useRef() 



    // 初始化灯光
    const createLight = useCallback(
        () => {
            // 太阳光
            const directionalLight = new THREE.DirectionalLight('#ffffff',1.2)
            directionalLight.position.set(0,0,3000)
            directionalLight.castShadow = true
            //投影产生阴影也是利用相机，而且相机默认范围比较小，所以要自定义
            directionalLight.shadow.camera.top = -10
            directionalLight.shadow.camera.bottom = 10
            directionalLight.shadow.camera.right = -10
            directionalLight.shadow.camera.left = 10
            directionalLight.shadow.mapSize.height = 1024
            directionalLight.shadow.mapSize.width = 1024

            // 环境光
            const ambientLight = new THREE.AmbientLight('#ffffff',0.6)

            // // 点光源
            // const pointLight = new THREE.PointLight( 0xffffff, 2, 8)
            // pointLight.position.set(0,5,0)

            Scene.add(
                directionalLight,
                ambientLight,
                // pointLight
                )
            Lights.push(
                directionalLight,
                ambientLight,
                // pointLight
                )
        },
        [],
    )
    //创建立方体
    const createRect = useCallback(
        () => {
            const rect = new THREE.BoxBufferGeometry( 2, 2, 2 )
            const meshBasicMater = new THREE.MeshBasicMaterial({color: 0x00ff00})
            const mesh = new THREE.Mesh( rect, meshBasicMater );
            mesh.position.set(0,0,0)
            // 可以投射阴影但不能接收阴影
            mesh.castShadow = true
            Scene.add( mesh );
            Meshs.push(mesh)
        },
        [],
    )

    //创建线条
    const createLine = useCallback(
        (x,y,z,color,num) => {
            const lineMater = new THREE.LineBasicMaterial({vertexColors:true})
            const geometry = new THREE.BufferGeometry()
            const width = Math.random() * 5
            let verticles =new Array()
            // let color = new THREE.Color();
            let color1 =new Array()
            for (let i=0;i<1000+num;i++){
                const x = Math.random() * width - width * 0.5
                const y = Math.random() * width - width * 0.5
                const z = Math.random() * width - width * 0.5
                verticles.push(x,y,z)
                // color.setRGB(Math.random(), Math.random(), Math.random());
                color1.push(color.r, color.g, color.b); 
            }
            geometry.setAttribute(
                'position',
                new THREE.Float32BufferAttribute(verticles, 3)
              );
            geometry.setAttribute('color', new THREE.Float32BufferAttribute(color1, 3)); 
            const mesh = new THREE.Line(geometry,lineMater)
            mesh.position.set(x,y,z)
            // 可以投射阴影但不能接收阴影
            mesh.castShadow = true
            Scene.add(mesh)
            Meshs.push(mesh)
        },
        [],
    )



    // 创建Lambert材质立方体
    const createLambert = useCallback(
        (x, y, z, color) => {
            const width = Math.random() * 5
           const lambert = new THREE.MeshLambertMaterial({color})
           const rect = new THREE.BoxBufferGeometry(width, width, width)
           const mesh =new THREE.Mesh(rect,lambert)
           mesh.position.set(x, y, z)
           mesh.castShadow = true
           mesh.receiveShadow = true
           Scene.add(mesh)
           Meshs.push(mesh)

        },
        [],
    )
    // 创建phong材质立方体
    const createPhong= useCallback(
        (x, y, z, color) => {
            const width = Math.random() * 5
           const phong = new THREE.MeshPhongMaterial({color})
           const rect = new THREE.BoxBufferGeometry(width, width, width)
           const mesh =new THREE.Mesh(rect,phong)
           mesh.position.set(x, y, z)
           mesh.castShadow = true
           mesh.receiveShadow = true
           Scene.add(mesh)
           Meshs.push(mesh)

        },
        [],
    )

    // 创建星球
    const createPlanet = useCallback(
        (x, y, z, color) => {
            const width = Math.random() * 2
            // 球体
            const geomatry = new THREE.SphereBufferGeometry(width, 64, 64)
            const phong = new THREE.MeshPhongMaterial({ color })
            const sphere = new THREE.Mesh(geomatry, phong)
            sphere.position.set(x, y, z)
            // 星云
            const geomatry2 = new THREE.RingGeometry(width + 0.3, width + 0.3 + width * 0.3, 64)
            //两个面都可以渲染 
            const lambert = new THREE.MeshLambertMaterial({ color: '#fff', side: THREE.DoubleSide })
            const ring = new THREE.Mesh(geomatry2, lambert)
            ring.position.set(x, y, z)
            ring.rotation.x = -90 * 180 / Math.PI

            const group = new THREE.Group()
            group.add(sphere, ring)
            Scene.add(group)
            
        },
        [],
    )

    // 设置鼠标事件
    const wheel = useCallback(
        (event) => {
            if (event.deltaY>0) pi.current += 1
            else pi.current -= 1
            const x = pi.current*Math.cos(R.current/180*Math.PI)
            const y = Camera.position.y
            const z = pi.current*Math.sin(R.current/180*Math.PI)

            Camera.position.set(x,y,z)
            Camera.lookAt(0,0,0)
            console.log(event);
        },
        [],
    )


    const down = useCallback(
        () => {
            isDown.current = true
        },
        [],
    )
    const up = useCallback(
        () => {
            isDown.current = false
        },
        [],
    )
    const move = useCallback(
        (event) => {
            if (isDown.current === false ) return
            R.current -= event.movementX*0.5
            const x = pi.current*Math.cos(R.current/180*Math.PI)
            const y = Camera.position.y + event.movementY*0.1
            const z = pi.current*Math.sin(R.current/180*Math.PI)

            Camera.position.set(x,y,z)
            Camera.lookAt(0,0,0)
        },
        [],
    )

    const click = useCallback(
        () => {
            // const array = [createLambert,createLine,createPhong]
            const index = Math.floor(Math.random()*3)
            
            const x = 20 - Math.random() * 40
            const y = 10 - Math.random() * 20
            const z = 20 - Math.random() * 40
            const color  = new THREE.Color(Math.random(), Math.random(), Math.random())
            // const num = index === 2 ? Math.ceil(Math.random() * 10000) : 0

            // array[index](x,y,z,color,num)
            createPlanet(x, y, z, color)
        },
        [],
    )
 
    // 初始化函数（设置宽高）
    // useCallback返回一个缓存的回调函数
    // 把内联回调函数及依赖项数组作为参数传入 useCallback，它将返回该回调函数的 memoized 版本，
    // 该回调函数仅在某个依赖项改变时才会更新。
    // 当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染（例如 shouldComponentUpdate）的子组件时，它将非常有用
    const init = useCallback(
        () => {
            Render.setSize(Body.current.offsetWidth,Body.current.offsetHeight)
            Render.shadowMap.enabled = true
            //设置相机参数
            Camera.aspect = Body.current.offsetWidth/Body.current.offsetHeight
            Camera.fov =45
            Camera.near = 1
            Camera.far = 1000
            Camera.position.set(0,3,pi.current)
            Camera.lookAt(0,0,0)
            Camera.updateProjectionMatrix()
        },
        [Render,Body],
    )

    // 渲染画面    
    const renderScene = useCallback(
        () => {
            Render.render(Scene,Camera)
            Meshs.forEach(
                item =>{
                    item.rotation.x += 1/180*Math.PI
                    item.rotation.y += 1/180*Math.PI
                }
            )
            id.current = window.requestAnimationFrame(() =>renderScene())
        },
        [Render,Meshs],
    )
    // .domElement : DOMElement
    // 一个canvas，渲染器在其上绘制输出。
    // 渲染器的构造函数会自动创建(如果没有传入canvas参数);你需要做的仅仅是像下面这样将它加页面里去:
    // document.body.appendChild( renderer.domElement );

    useEffect(() => {
        //渲染器的canvas添加到div里面去
        Body.current.append(Render.domElement)
        init()
        createLight()
        renderScene()
        
        // 销毁钩子
        return () => {
            // 生命周期卸载时调用
            //
            cancelAnimationFrame(id.current)
            Meshs.forEach(
                item => {
                    Scene.remove(item)
                    item.geometry.dispose()
                    item.material.dispose()
                }
            )
            Lights.forEach(
                item => Scene.remove(item)
            )
            Render.dispose()
            Scene.dispose()
                    
        }
    }, [])


    return (
        <div className="page" ref={Body} onMouseDown={down} onMouseUp={up} onMouseMove={move} onWheel={wheel} onClick={click}>
            
        </div>
    )
}
